package component

import (
	"bufio"
	"cke.io/ckectl/app/apis/v1alpha1"
	ckectl "cke.io/ckectl/app/cmd/ckectl"
	"cke.io/ckectl/app/util"
	"encoding/base64"
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	flag "github.com/spf13/pflag"
	"io"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"time"
)

const (
	CkeComponentScriptPath      = "/opt/ckeadm-script"
	CkectlScriptPath            = "/opt/ckectl-script"
	CkeComponentScriptImageRepo = "library/cke/components/"
)

func NewCmdComponent() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "component",
		Short: "",

		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			return nil
		},
	}
	cmd.AddCommand(NewCmdComponentCreate())
	cmd.AddCommand(NewCmdComponentDelete())
	cmd.AddCommand(NewCmdComponentUpgrade())
	cmd.AddCommand(NewCmdComponentUpdate())
	cmd.AddCommand(NewCmdComponentScaleup())
	cmd.AddCommand(NewCmdComponentScaledown())
	//cmd.AddCommand(NewCmdComponentMaster2Slave())
	//cmd.AddCommand(NewCmdComponentSlave2Master())
	return cmd
}

type componentOptions struct {
	ckeClusterCode   string
	ckeNodeCode      string
	ckeComponentCode string
	ckeDir           string
	ckeKey           string
	ckeNodeName      string
	ckeAddress       string
	ckePort          int
	suffix           string
}

var componentOption = &componentOptions{}

func AddComponentFlags(flagSet *flag.FlagSet) {
	flagSet.StringVar(
		&componentOption.ckeClusterCode, "cluster", "",
		"CkeCluster CR string encoded with base64",
	)
	flagSet.StringVar(
		&componentOption.ckeNodeCode, "node", "",
		`CkeNode CR string encoded with base64`,
	)
	flagSet.StringVar(
		&componentOption.ckeComponentCode, "component", "",
		"CkeComponent CR string encoded with base64",
	)
	flagSet.StringVar(
		&componentOption.ckeDir, "dir", "",
		"",
	)
	flagSet.StringVar(
		&componentOption.ckeKey, "key", "",
		"",
	)
	flagSet.StringVar(
		&componentOption.ckeNodeName, "nodeName", "",
		"",
	)
	flagSet.StringVar(
		&componentOption.ckeAddress, "address", "",
		"",
	)
	flagSet.IntVar(
		&componentOption.ckePort, "port", 0,
		"",
	)
	flagSet.StringVar(
		&componentOption.suffix, "suffix", "",
		"",
	)
}

func ckectlComponentAction(action string) error {
	var err error
	var ckeCluster v1alpha1.CkeCluster
	var ckeComponent v1alpha1.CkeComponent
	var curNode v1alpha1.CkeNode
	ckeNode := new([]v1alpha1.CkeNode)
	ckeClusterByte, err := base64.StdEncoding.DecodeString(componentOption.ckeClusterCode)
	if err != nil {
		logrus.Error("error decode CkeCluster base64 code: " + err.Error())
		return err
	}
	err = ckectl.GetCkeClusterFromYaml(ckeClusterByte, &ckeCluster)
	if err != nil {
		logrus.Error("error unmarshal ckeCluster: " + err.Error())
		return err
	}
	ckeComponentByte, err := base64.StdEncoding.DecodeString(componentOption.ckeComponentCode)
	if err != nil {
		logrus.Error("error decode CkeComponent base64 code: " + err.Error())
		return err
	}
	err = ckectl.GetSingleCkeComponentFromYaml(ckeComponentByte, &ckeComponent)
	if err != nil {
		logrus.Error("error unmarshal ckeComponent: " + err.Error())
		return err
	}
	ckeNodesByte, err := base64.StdEncoding.DecodeString(componentOption.ckeNodeCode)
	if err != nil {
		logrus.Error("error decode CkeNode base64 code: " + err.Error())
		return err
	}
	err = ckectl.GetCkeNodeFromYaml(ckeNodesByte, ckeNode)
	if err != nil {
		logrus.Error("error unmarshal ckeNodes: " + err.Error())
		return err
	}
	var scaleupNode string
	var scaledownNode string
	for _, node := range *ckeNode {
		if node.Name == componentOption.ckeNodeName {
			curNode = node
		}
		if action == "scaleup" && strings.Contains(strings.Join(node.Spec.Roles, ","), "kube-scale") {
			scaleupNode = node.Name
		}
		if action == "scaledown" && strings.Contains(strings.Join(node.Spec.Roles, ","), "kube-scaledown") {
			scaledownNode = node.Name
		}
	}
	logrus.Infof("--- ckectl component %s start ---", action)
	logrus.Infof("--- component: %s, node: %s, role: %s ---", ckeComponent.Name, componentOption.ckeNodeName, componentOption.ckeDir)
	suffix := util.RandString(6)
	if componentOption.suffix != "" {
		suffix = componentOption.suffix
	}
	logChan := make(chan util.LogChan)
	done := make(chan bool)
	go util.SendComponentLog(&ckeCluster, &curNode, &ckeComponent, componentOption.ckeAddress, componentOption.ckePort, suffix, action, logChan, done)
	go func(ch chan util.LogChan) {
		for {
			time.Sleep(3 * time.Minute)
			logChan <- util.LogChan{
				Log:   fmt.Sprintf("#####FORCE_SEND_LOG#####"),
				Final: false,
			}
		}
	}(logChan)
	logChan <- util.LogChan{
		Log:   fmt.Sprintf("--- role: %s ---", componentOption.ckeDir),
		Final: false,
	}
	if action == "scaleup" {
		logChan <- util.LogChan{
			Log:   fmt.Sprintf("--- scaleup node: %s ---", scaleupNode),
			Final: false,
		}
	}
	if action == "scaledown" {
		logChan <- util.LogChan{
			Log:   fmt.Sprintf("--- scaledown node: %s ---", scaledownNode),
			Final: false,
		}
	}
	path := CkectlScriptPath + "/" + ckeComponent.Name + "/" + action + "/" + componentOption.ckeDir
	if _, ok := ckeComponent.ObjectMeta.Labels["containerRuntime"]; (!ok) || (ckeComponent.ObjectMeta.Labels["containerRuntime"] != "true") {
		containerName := ckeComponent.Name + "-template"
		err = ExtractScript(containerName, GetImageName(ckeCluster, ckeComponent), ckeComponent)
		if err != nil {
			logContent := fmt.Sprintf("ExtractScript failed: %s", err.Error())
			logrus.Error(logContent)
			logChan <- util.LogChan{
				Log:   logContent,
				Final: true,
			}
			<-done
			return err
		}
	}
	err = util.ProcessTemplate(ckeCluster, ckeComponent, *ckeNode, curNode, CkectlScriptPath+"/"+ckeComponent.Name, componentOption.ckeKey)
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("ProcessTemplate failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}

	entryFile := path + "/" + action + ".sh"
	err = os.Chmod(entryFile, 0755)
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("Chmod failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}
	// #nosec
	bashCmd := exec.Command(entryFile)
	stdout, err := bashCmd.StdoutPipe()
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("Set stdout pipe failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}
	stderr, err := bashCmd.StderrPipe()
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("Set stderr pipe failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}
	output := io.MultiReader(stdout, stderr)
	err = bashCmd.Start()
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("bashCmd.Start() failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}
	reader := bufio.NewReader(output)
	for {
		line, err := reader.ReadString('\n')
		if err != nil || io.EOF == err {
			break
		}
		logrus.Info(line)
		logChan <- util.LogChan{
			Log:   line,
			Final: false,
		}
	}
	err = bashCmd.Wait()
	if err != nil {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("bashCmd.Wait() failed: %s", err.Error())
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return err
	}
	exitCode := bashCmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
	if exitCode != 0 {
		RemoveComponentScript(ckeComponent.Name)
		logContent := fmt.Sprintf("Execute bash failed, exit code: %d", exitCode)
		logrus.Error(logContent)
		logChan <- util.LogChan{
			Log:   logContent,
			Final: true,
		}
		<-done
		return fmt.Errorf(logContent)
	}
	RemoveComponentScript(ckeComponent.Name)
	logChan <- util.LogChan{
		Log:   "--- ckectl component " + action + " end ---",
		Final: true,
	}
	<-done
	logrus.Infof("--- component: %s, node: %s, role: %s ---", ckeComponent.Name, componentOption.ckeNodeName, componentOption.ckeDir)
	logrus.Infof("--- ckectl component %s end ---", action)
	return nil
}

func RemoveComponentScript(component string) {
	_ = os.RemoveAll(CkectlScriptPath + "/" + component)
}
